package jdec.jdt.ls.decompiler.common.util;


import java.util.Arrays;
import java.util.HashMap;
import org.eclipse.jdt.core.IBuffer;
import org.eclipse.jdt.core.IClassFile;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.internal.compiler.env.IBinaryType;
import org.eclipse.jdt.internal.core.BufferManager;
import org.eclipse.jdt.internal.core.ClassFile;
import org.eclipse.jdt.internal.core.NamedMember;
import org.eclipse.jdt.internal.core.SourceMapper;
import jdec.jdt.ls.decompiler.common.JavaDecompilerBufferManager;


public class SourceMapperUtil {

	public static void mapSourceMapper(SourceMapper sourceMapper, IType type, char[] source, IBinaryType info) {
		if (sourceMapper != null) {
			HashMap sourceRange = (HashMap) ReflectionUtils.getFieldValue(sourceMapper, "sourceRanges"); //$NON-NLS-1$
			sourceRange.remove(type);
			sourceMapper.mapSource((NamedMember) type, source, info);
		}
	}

	public static void updateSourceRangesFromParentClassFile(IType type, char[] attachedSource) {
        if (type.getParent() instanceof ClassFile) {
            try {
                SourceMapperUtil.updateSourceRanges((ClassFile) type.getParent(), new String(attachedSource));
            } catch (JavaModelException e) {
                Logger.debug(e);
            }
        }
    }

	public static void updateSourceRanges(IClassFile cf, String contents) throws JavaModelException {
		if (cf instanceof ClassFile) {
			ClassFile classFile = (ClassFile) cf;
			Object info = classFile.getElementInfo();
			IBinaryType typeInfo = info instanceof IBinaryType ? (IBinaryType) info : null;
			SourceMapper mapper = classFile.getSourceMapper();
			IType type = getOuterMostEnclosingType(classFile);
			mapSourceMapper(mapper, type, contents.toCharArray(), typeInfo);
		}
	}

	/** Returns the type of the top-level declaring class used to find the source code */
	public static IType getOuterMostEnclosingType(ClassFile cf) {
		IType type = cf.getType();
		IType enclosingType = type.getDeclaringType();
		while (enclosingType != null) {
			type = enclosingType;
			enclosingType = type.getDeclaringType();
		}
		return type;
	}

	private static void bufferManagerMapSource(JavaDecompilerBufferManager bufferManager, ClassFile cf, SourceMapper mapper,
			IBinaryType info, IClassFile bufferOwner, char[] markedSrc) {
		char[] contents = mapper.findSource(cf.getType(), info);
		if (Arrays.equals(markedSrc, contents)) {
			return;
		}
		contents = markedSrc;
		if (contents != null) {
			// create buffer
			IBuffer buffer = BufferManager.createBuffer(bufferOwner);
			if (buffer == null) {
				return;
			}
			JavaDecompilerBufferManager bufManager = bufferManager;
			bufManager.addBuffer(buffer);

			// set the buffer source
			if (buffer.getCharacters() == null) {
				buffer.setContents(contents);
			}

			// do the source mapping
			mapSourceMapper(mapper, getOuterMostEnclosingType(cf), contents, info);
		} else {
			// create buffer
			IBuffer buffer = BufferManager.createNullBuffer(bufferOwner);
			if (buffer == null) {
				return;
			}
			JavaDecompilerBufferManager bufManager = bufferManager;
			bufManager.addBuffer(buffer);
		}
	}

	public static void bufferManagerUpdateSource(JavaDecompilerBufferManager bufferManager, ClassFile cf, char[] markedSrc)
			throws JavaModelException {
		IType type = cf.getType();
		if (!type.isBinary()) {
			return;
		}
		Object info = cf.getElementInfo();
		IType outerMostEnclosingType = getOuterMostEnclosingType(cf);
		SourceMapper mapper = cf.getSourceMapper();
		IBinaryType typeInfo = info instanceof IBinaryType ? (IBinaryType) info : null;
		if (mapper != null) {
			bufferManagerMapSource(bufferManager, cf, mapper, typeInfo, outerMostEnclosingType.getClassFile(), markedSrc);
		}
	}

}
